import { CSLine } from '@/common/basic';
import { useRenderStore } from '@/common/store';
import { nmAppLog } from '@/common/utils';
import { EntityInfo, EntityPartType, THREE } from '@amcax/web-render';
import { ManagerId } from 'nextcae-base';

/**
 * 根据两个三维空间点和相机视线方向，在给定包围盒范围内创建一个矩形平面。
 *
 * 该平面满足以下特点：
 * - 宽度方向（X 轴）为连接两点的向量方向（即两点连线方向）
 * - 高度方向（Y 轴）为垂直于相机视线方向（法线）和宽度方向的向量
 * - 法线方向（Z 轴）为经过调整后的相机视线方向，且垂直于宽度方向
 * - 尺寸基于传入包围盒尺寸，并额外扩展一定比例以覆盖视图范围
 * - 支持围绕宽度方向旋转法线的角度调整
 *
 * @param twoPoints - 长度为2的数组，包含两个世界坐标点，确定平面上的两点（AB）
 * @param initialNormal - 初始法线向量（通常为相机视线方向）
 * @param box - 用于计算平面尺寸的 THREE.Box3 包围盒，一般为场景或模型整体包围盒
 * @param angleDeg - 可选，绕宽度方向旋转法线的角度（单位：度），默认为0
 * @returns 返回一个 THREE.Mesh 实例，代表构造出的矩形平面；如果输入点不足两个则返回 null
 */
export function createPlane(
  twoPoints: THREE.Vector3Tuple[],
  initialNormal: THREE.Vector3,
  size: THREE.Vector3,
  angleDeg = 0, // 新增参数，角度，默认 0
) {
  if (twoPoints.length !== 2) {
    return null;
  }
  // 已知数据
  const pointA = new THREE.Vector3(...twoPoints[0]);
  const pointB = new THREE.Vector3(...twoPoints[1]);

  // 构造平面坐标轴
  const xAxis = new THREE.Vector3().subVectors(pointB, pointA).normalize(); // AB方向
  // 先确保初始法向量垂直于 xAxis
  const projectedNormal = new THREE.Vector3()
    .copy(initialNormal)
    .sub(xAxis.clone().multiplyScalar(initialNormal.dot(xAxis)))
    .normalize();

  // 按角度旋转法向量
  const normal = projectedNormal
    .clone()
    .applyAxisAngle(xAxis, THREE.MathUtils.degToRad(angleDeg));

  // 平面中心（任选，可以是中点）
  const center = new THREE.Vector3()
    .addVectors(pointA, pointB)
    .multiplyScalar(0.5);

  // 计算平面局部 Y 方向（与法向垂直且与 xAxis 垂直）
  const yAxis = new THREE.Vector3().crossVectors(normal, xAxis).normalize();

  // 构造变换矩阵
  const basis = new THREE.Matrix4().makeBasis(xAxis, yAxis, normal); // 构造一个局部坐标系统
  const transform = new THREE.Matrix4().multiply(basis);

  const width = Math.max(size.x, size.z) * 1.1; // 多留 10% 空间
  const height = size.y * 1.1;

  const geometry = new THREE.PlaneGeometry(width, height); // 默认在 XY 平面
  geometry.applyMatrix4(transform); // 应用变换

  const material = new THREE.MeshBasicMaterial({
    color: 0x00ff00,
    side: THREE.DoubleSide,
    transparent: true,
    opacity: 0.5,
  });

  const plane = new THREE.Mesh(geometry, material);
  plane.position.copy(center);
  return plane;
}
/**
 * 获取平面的法向
 * @returns 法向的数组
 */
export function getPlaneWorldNormal(planeMesh: THREE.Mesh): THREE.Vector3Tuple {
  const normalAttr = (planeMesh.geometry as THREE.BufferGeometry).attributes
    .normal;
  if (normalAttr.count >= 1) {
    const normal = new THREE.Vector3().fromBufferAttribute(normalAttr, 0);
    return normal.toArray();
  }
  return null;
}
/**
 * 创建一个过 origin，法向为 dir 的平面 Mesh
 * @param origin - 平面上一点
 * @param dir - 平面法向
 * @param size - 平面大小
 * @param color - 平面颜色
 * @returns THREE.Mesh
 */
export function makePlane(
  origin: THREE.Vector3,
  dir: THREE.Vector3,
  size: THREE.Vector3 = new THREE.Vector3(10, 10, 10),
  color: number = 0x00ff00,
): THREE.Mesh<THREE.PlaneGeometry, THREE.MeshBasicMaterial> {
  // 1. 平面几何
  const width = Math.max(size.x, size.z) * 1.1; // 多留 10% 空间
  const height = size.y * 1.1;

  const geometry = new THREE.PlaneGeometry(width, height);

  // 2. 材质
  const material = new THREE.MeshBasicMaterial({
    color,
    side: THREE.DoubleSide,
    transparent: true,
    opacity: 0.3,
  });

  // 3. Mesh
  const mesh = new THREE.Mesh(geometry, material);

  // 4. 旋转到 dir 方向
  const normal = dir.clone().normalize();
  const defaultNormal = new THREE.Vector3(0, 0, 1); // PlaneGeometry 默认法向
  const quaternion = new THREE.Quaternion().setFromUnitVectors(
    defaultNormal,
    normal,
  );
  mesh.quaternion.copy(quaternion);

  // 5. 移动到 origin
  mesh.position.copy(origin);

  return mesh;
}
/**
 * 在平面上绘制多条角度线
 * @param origin - 平面上的中心点
 * @param normal - 平面法向
 * @param refDir0 - 平面内的 0° 方向
 * @param angles - 角度数组（单位：度）
 * @param length - 每条线的一半长度
 * @param color - 线条颜色
 * @returns THREE.LineSegments
 */
export function makeAngleLines(
  origin: THREE.Vector3,
  normal: THREE.Vector3,
  refDir0: THREE.Vector3,
  angles: number[],
  length: number = 10,
  color: number = 0x0000ff,
): CSLine {
  // 归一化
  const n = normal.clone().normalize();
  const u = refDir0.clone().normalize();
  const v = new THREE.Vector3().crossVectors(n, u).normalize();

  const vertexes: THREE.Vector3Tuple[] = [];

  for (const deg of angles) {
    const rad = THREE.MathUtils.degToRad(deg);
    // 在平面内旋转
    const dir = u
      .clone()
      .multiplyScalar(Math.cos(rad))
      .add(v.clone().multiplyScalar(Math.sin(rad)));

    const p1 = origin.clone().add(dir.clone().multiplyScalar(length));

    vertexes.push(origin.toArray(), p1.toArray());
  }
  const line = new CSLine({
    vertexes,
    color,
    depthTest: true,
    lineWidth: 1,
    type: 'segments',
  });
  line.setOpacity(0.3);
  return line;
}

/**
 * 如果有吸附到的端点，则根据吸附的点，获取该点所能hover的单元信息
 */
export const handleSnapPoint = (vertexPoint: THREE.Vector3Tuple) => {
  if (!vertexPoint) {
    return null;
  }
  const mainRenderer = useRenderStore.getState().mainRenderer;
  const camera = mainRenderer.cameraManager.camera;
  const container = mainRenderer.container;
  const interactionMgr = mainRenderer.interactionManager;
  // 端点如果有数据则将端数据存在绘制线的数组里。以及对应的网格单元idx
  // 获取点所在屏幕的坐标x，y
  const coordinate = getScreenCoordinate(
    new THREE.Vector3(...vertexPoint),
    camera,
    container,
  );
  // 获取对应点的网格单元信息
  const elements = interactionMgr.pickAt(coordinate, [EntityPartType.element]);
  nmAppLog('获取对应点的网格单元信息：', elements);
  if (elements.length > 0) {
    // 返回第一个 element，将element的point更新为vertexPoint，使得完全一致
    elements[0].point = vertexPoint;
    return elements[0];
  }

  return null;
};

// 空间点转换为画布上的屏幕坐标
const getScreenCoordinate = (
  point: THREE.Vector3,
  camera: THREE.Camera,
  container: HTMLElement,
) => {
  // 转换目标点到屏幕坐标
  // 获取相机最新坐标
  camera.updateMatrixWorld();
  // 克隆坐标防止修改原始向量
  const projected = point.clone().project(camera);
  // 画布尺寸
  const width = container.clientWidth;
  const height = container.clientHeight;
  // 将 NDC（-1~1） 转换为屏幕坐标（像素），计算在画布位置
  const x = ((projected.x + 1) / 2) * width;
  const y = ((1 - projected.y) / 2) * height; // 注意 Y 轴反转
  return { x, y };
};

/**
 * 查找最近的点
 * @param targetPoint 目标点
 * @param points 点数组
 * @param screenThreshold 屏幕阈值
 * @returns 最近的点，不在阈值内返回{point:null,index:-1}
 */
export const findClosestPoint = (
  targetPoint: THREE.Vector3Tuple,
  points: THREE.Vector3Tuple[],
  screenThreshold: number = 20,
) => {
  const mainRenderer = useRenderStore.getState().mainRenderer;
  const camera = mainRenderer.cameraManager.camera;
  const container = mainRenderer.container;
  // 转换目标点到屏幕坐标
  const targetScreenPos = getScreenCoordinate(
    new THREE.Vector3(...targetPoint),
    camera,
    container,
  );
  let closestPointIndex = -1;
  let closestPoint: THREE.Vector3Tuple = null;
  let minScreenDistance = Infinity;
  // 吸附线的节点
  for (let i = 0; i < points.length; i++) {
    const point = points[i];
    const worldPos = new THREE.Vector3(...point);
    // 转换为屏幕坐标
    const screenPos = getScreenCoordinate(worldPos, camera, container);
    // 计算屏幕空间距离（忽略Z轴）
    const screenDistance = Math.sqrt(
      Math.pow(screenPos.x - targetScreenPos.x, 2) +
        Math.pow(screenPos.y - targetScreenPos.y, 2),
    );
    if (
      screenDistance < screenThreshold &&
      screenDistance < minScreenDistance
    ) {
      0;
      closestPointIndex = i;
      closestPoint = worldPos.toArray();
      minScreenDistance = screenDistance;
      nmAppLog(
        `点 ${point} 屏幕距离 ${screenDistance.toFixed(2)}px < ${screenThreshold}px`,
      );
    }
  }
  if (closestPoint) {
    nmAppLog('找到最近吸附点:', closestPoint);
    return {
      point: closestPoint,
      index: closestPointIndex,
    };
  }
  return {
    point: null,
    index: -1,
  };
};

/**
 * 吸附点（包含世界坐标到屏幕坐标转换）；
 * 优先吸附到线的端点
 * @param e 拾取的entity信息
 * @param screenThreshold 屏幕阈值
 * @returns 最近的点，不在阈值内返回null
 */
export const snapPoint = (e: EntityInfo, screenThreshold: number = 20) => {
  const { type, dxid, idx, point } = e;
  const mainRenderer = useRenderStore.getState().mainRenderer;
  const meshMgr = mainRenderer.getEntityManager(ManagerId.Mesh);

  if (type === EntityPartType.line) {
    // 获取线的点数据信息
    const lineData = meshMgr.getEntityData(dxid, type, idx);
    if (lineData?.length > 0) {
      // 转换为二维数组
      const resultArray: THREE.Vector3Tuple[] = [];
      for (let i = 0; i < lineData.length; i += 3) {
        resultArray.push([
          lineData[i], // x
          lineData[i + 1], // y
          lineData[i + 2], // z
        ]);
      }
      const { point: closestPoint } = findClosestPoint(
        point,
        resultArray,
        screenThreshold,
      );
      if (closestPoint) {
        nmAppLog('找到最近吸附点:', closestPoint);
        return closestPoint;
      }
    }
  }
  return null;
};
